/*

  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at 
  
           http://www.mozilla.org/MPL/ 
  
  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the License. 
  
  The Original Code is Indigo framework applicatif.
  
  The Initial Developer of the Original Code is
  Olivier Bugalotto (aka Iteratif) <olivier.bugalotto@iteratif.net>.
  Portions created by the Initial Developer are Copyright (C) 2004-2008
  the Initial Developer. All Rights Reserved.
  
  Contributor(s) :
  
*/
package indigo.descriptors
{
	import indigo.collections.ArrayList;
	import indigo.collections.Hashtable;
	import indigo.collections.IDictionary;
	import indigo.config.ArrayProperty;
	import indigo.config.IObjectDefinition;
	import indigo.config.ListProperty;
	import indigo.config.ObjectDefinition;
	import indigo.config.Property;
	import indigo.config.ReferenceProperty;
	import indigo.config.ReferenceValue;
	import indigo.register.IObjectDefinitionRegistry;
	
	/**
	 * English:
	 * The class <code>ObjectDefinitionDescriptor</code> allows the reading of 
	 * a descriptor XML for the repository of the lightweight container
	 * 
	 * @example if you would creating others descriptors for example descriptor JSON,
	 * this is very easy, you can create a class which implements the interface <code>IDescriptor</code>:
	 * 
	 * <listing version="3.0">
	 * <b>package</b> indigo.descriptors {
	 * 		<b>public class</b> JSONDescriptor <b>implements</b> IDescriptor {
	 * 			<b>public function</b> parse(source:Object):<b>void</b> {
	 * 
	 * 			}
	 * 		}
	 * }
	 * </listing>
	 * 
	 * French:
	 * La classe <code>ObjectDefinitionDescriptor</code> permet la lecture
	 * d'un descripteur de configuration XML pour alimenter le referentiel
	 * de la fabrique IOC.
	 * 
	 * @example Si vous voulez creer d'autres descripteurs de configuration par exemple
	 * pour du JSON, rien de plus facile, il vous suffit creer une classe
	 * qui implemente l'interface <code>IDescriptor</code> :
	 * 
	 * <listing version="3.0">
	 * <b>package</b> indigo.descriptors {
	 * 		<b>public class</b> JSONDescriptor <b>implements</b> IDescriptor {
	 * 			<b>public function</b> parse(source:Object):<b>void</b> {
	 * 
	 * 			}
	 * 		}
	 * }
	 * </listing>
	 * 
	 * @see IDescriptor
	 */
	public class ObjectDefinitionDescriptor implements IDescriptor
	{	
		protected namespace xmlns = "http://www.indigo-framework.org";
		protected const qImports:QName = new QName(xmlns,NodeKind.IMPORT);
		protected const qObjects:QName = new QName(xmlns,NodeKind.OBJECTS);
		protected const qObject:QName = new QName(xmlns,NodeKind.OBJECT);
		protected const qProperty:QName = new QName(xmlns,NodeKind.PROPERTY);
		protected const qXmlList:QName = new QName(xmlns,NodeKind.XMLLIST);
		protected const qEntry:QName = new QName(xmlns,NodeKind.ENTRY);
		protected const qValue:QName = new QName(xmlns,NodeKind.VALUE);
		protected const qRef:QName = new QName(xmlns,NodeKind.REF);
		
		protected var _registry:IObjectDefinitionRegistry;
		
		/**
		 * en:
		 * Get the reference of repository
		 * @return reference of the object <code>IObjectDefinitionRegistry</code>
		 * 
		 * 
		 * fr:
		 * Obtenir la reference au referentiel d'objets. 
		 * @return Reference d'un objet <code>IObjectDefinitionRegistry</code>
		 * 
		 * @see indigo.registry.IObjectDefinitionRegistry
		 */
		public function get registry():IObjectDefinitionRegistry {
			return _registry;
		}
		
		/**
		 * en:
		 * Create a object <code>ObjectDefinitionDescriptor</code> which fills
		 * the repository of object definitions.
		 * 
		 * fr:
		 * Creer un objet <code>ObjectDefinitionDescriptor</code> qui alimente
		 * le referentiel de definitions d'objet qu'utilise la fabrique IOC <code>SimpleObjectFactory</code> 
		 * @param registry	Reference d'un objet <code>IObjectDefinitionRegistry</code>.
		 * 
		 * @see indigo.factory.SimpleObjectFactory
		 */
		public function ObjectDefinitionDescriptor(registry:IObjectDefinitionRegistry)
		{
			_registry = registry;
			
		}

		/** 
		 * fr:
		 * Parcourt un descripteur de configuration XML pour construire des objets <code>IObjectDefinition</code>
		 * qui seront stokés dans le referentiel.
		 * 
		 * @param source Source brut a parcourir
		 * 
		 */
		public function parse(source:Object):void {			
			// TODO: Lecture des ressources importees
			// <configuration xmlns="http://www.indigo-framework.org">
			// 		<objects>
			// 			<import resource="config-objets.xml"/>
			//			...
			// 		</objects>
			// </configuration>
			var root:XML = XML(source);
			parseImports(root.elements(NodeKind.IMPORT));
			
			// Gestion de la compatibilité avec la version b1 -------
			var rootName:String = QName(root.name()).localName;
			if(rootName == NodeKind.CONFIGURATION)
				root = root.descendants(qObjects)[0];
			// ------------------------------------------------------
			parseObjectDefinitions(root.child(qObject));
		}
		
		protected function parseObjectDefinitions(objects:XMLList):void {
			for each(var object:XML in objects) {
				parseObjectDefinition(object);
			}
		}
		
		protected function parseObjectDefinition(object:XML):void {
			if(!object.hasOwnProperty(AttributKind.formatE4X(AttributKind.ID_ATTR))) throw new Error("La definition suivante ne contient pas d'attribute id: " + object);
			if(!object.hasOwnProperty(AttributKind.formatE4X(AttributKind.TYPE_ATTR))) throw new Error("La definition suivante ne contient pas d'attribute type: " + object);
			
			var id:String = object.attribute(AttributKind.ID_ATTR);
			var type:String = object.attribute(AttributKind.TYPE_ATTR);
			
			var singleton:Boolean = false;
			if(object.hasOwnProperty(AttributKind.formatE4X(AttributKind.SINGL_ATTR))) {
				singleton = parseBoolean(object.attribute(AttributKind.SINGL_ATTR));
			}
			var lazyInit:Boolean = parseBoolean(object.attribute(AttributKind.LAZY_ATTR));
			var initMethod:String = object.attribute(AttributKind.INIT_METHOD_ATTR);
			
			var properties:Array = parseProperties(object);
			
			var definition:IObjectDefinition = new ObjectDefinition(type,singleton,properties,lazyInit);
			definition.initMethod = initMethod;
			_registry.registerDefinition(id,definition);
		}
		
		protected function parseProperties(object:XML):Array {
			var props:ArrayList = new ArrayList();
			var properties:XMLList = object.child(qProperty);
			for each(var property:XML in properties) {
				props.add(parseProperty(property));
			}
			return props.arrayTo();
		}
		
		protected function parseProperty(property:XML):Property {
			var propertyName:String = property.@[AttributKind.NAME_ATTR];
			
			var attrValue:XML = property.@[AttributKind.VALUE_ATTR][0];
			if(attrValue)
				return new Property(propertyName,attrValue.toString()); // TODO: gestion des types Number et Boolean
			
			var attrRef:XML = property.@[AttributKind.REF_ATTR][0];
			if(attrRef)
				return new ReferenceProperty(propertyName,attrRef.toString());
				
			// TODO: voir pour une implémentation des States
			if(property.hasComplexContent()) {
				var node:XML = property.children()[0]; // first node
				var nodeName:String = node.localName();
				switch(nodeName) {
					case NodeKind.XMLLIST:
						return new Property(propertyName,node.children());
					case NodeKind.MAP:
						return new Property(propertyName,parseMapNode(node));
					case NodeKind.ARRAY:
						return new ArrayProperty(propertyName,parseArrayNode(node));
					case NodeKind.LIST:
						var list:IList = new ArrayList(parseArrayNode(node));
						return new ListProperty(propertyName,list);
					default:
						return new Property(propertyName,parseValue(node));
				}
			}
		
			return null;
		}
		
		protected function parseArrayNode(node:XML):Array {
			var arr:Array = [];
			var children:XMLList = node.children();
			for each(var child:XML in children) {
				arr.push(parseValue(child));
			}
			return arr;
		}
		
		protected function parseValue(value:XML):* {
			var childName:String = value.localName();
			switch(childName) {
				case NodeKind.STRING:
					return value.toString();
				case NodeKind.BOOLEAN:
					return value.toString() == "true";
				case NodeKind.NUMBER:
					return parseFloat(value.toString());
				case NodeKind.OBJECT:
					return parseObject(value);
				case NodeKind.REF:
					return new ReferenceValue(value.@[AttributKind.NAME_ATTR][0]);
			}
		}
		
		protected function parseMapNode(node:XML):IDictionary {
			var map:IDictionary = new Hashtable();
			var entries:XMLList = node.child(qEntry);
			for each(var entry:XML in entries) {
				map.addItem(entry.@[AttributKind.KEY_ATTR][0].toString(),parseEntry(entry)); // TODO: gestion du node entry pour des types complexes
			}
			return map;
		}
		
		protected function parseEntry(entry:XML):* {
			var attrValue:String = entry.@[AttributKind.VALUE_ATTR];
			if(attrValue)
				return attrValue;
				
			if(entry.hasComplexContent()) {
				var item:XML = entry.children()[0];
				return parseValue(item);
			}
		}
		
		protected function parseObject(o:XML):Object {
			var object:Object = {};
			var attributs:XMLList = o.attributes();
			for each(var attr:XML in attributs) {
				object[attr.name().toString()] = attr.toString();
			}
			return object;
		}
		
		protected function parseBoolean(attr:String):Boolean {
			return (attr == "true");
		}
		
		protected function parseImports(imports:XMLList):void {
			
		}
	}
}